/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.agendadata;


import static com.example.android.agendadata.Constants.TAG;
import static com.example.android.agendadata.Constants.CONNECTION_TIME_OUT_MS;
import static com.example.android.agendadata.Constants.CAL_DATA_ITEM_PATH_PREFIX;
import static com.example.android.agendadata.Constants.ALL_DAY;
import static com.example.android.agendadata.Constants.BEGIN;
import static com.example.android.agendadata.Constants.DATA_ITEM_URI;
import static com.example.android.agendadata.Constants.DESCRIPTION;
import static com.example.android.agendadata.Constants.END;
import static com.example.android.agendadata.Constants.EVENT_ID;
import static com.example.android.agendadata.Constants.ID;
import static com.example.android.agendadata.Constants.PROFILE_PIC;
import static com.example.android.agendadata.Constants.TITLE;

import android.app.IntentService;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.provider.CalendarContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.text.format.Time;
import android.util.Log;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.Wearable;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Queries calendar events using Android Calendar Provider API and creates a data item for each
 * event.
 */
public class CalendarQueryService extends IntentService
        implements ConnectionCallbacks, OnConnectionFailedListener {

    private static final String[] INSTANCE_PROJECTION = {
            CalendarContract.Instances._ID,
            CalendarContract.Instances.EVENT_ID,
            CalendarContract.Instances.TITLE,
            CalendarContract.Instances.BEGIN,
            CalendarContract.Instances.END,
            CalendarContract.Instances.ALL_DAY,
            CalendarContract.Instances.DESCRIPTION,
            CalendarContract.Instances.ORGANIZER
    };

    private static final String[] CONTACT_PROJECTION = new String[] { Data._ID, Data.CONTACT_ID };
    private static final String CONTACT_SELECTION = Email.ADDRESS + " = ?";

    private GoogleApiClient mGoogleApiClient;

    public CalendarQueryService() {
        super(CalendarQueryService.class.getSimpleName());
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Wearable.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        mGoogleApiClient.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
        // Query calendar events in the next 24 hours.
        Time time = new Time();
        time.setToNow();
        long beginTime = time.toMillis(true);
        time.monthDay++;
        time.normalize(true);
        long endTime = time.normalize(true);

        List<Event> events = queryEvents(this, beginTime, endTime);
        for (Event event : events) {
            final PutDataMapRequest putDataMapRequest = event.toPutDataMapRequest();
            if (mGoogleApiClient.isConnected()) {
                Wearable.DataApi.putDataItem(
                    mGoogleApiClient, putDataMapRequest.asPutDataRequest()).await();
            } else {
                Log.e(TAG, "Failed to send data item: " + putDataMapRequest
                         + " - Client disconnected from Google Play Services");
            }
        }
        mGoogleApiClient.disconnect();
    }

    private static String makeDataItemPath(long eventId, long beginTime) {
        return CAL_DATA_ITEM_PATH_PREFIX + eventId + "/" + beginTime;
    }

    private static List<Event> queryEvents(Context context, long beginTime, long endTime) {
        ContentResolver contentResolver = context.getContentResolver();
        Uri.Builder builder = CalendarContract.Instances.CONTENT_URI.buildUpon();
        ContentUris.appendId(builder, beginTime);
        ContentUris.appendId(builder, endTime);

        Cursor cursor = contentResolver.query(builder.build(), INSTANCE_PROJECTION,
                null /* selection */, null /* selectionArgs */, null /* sortOrder */);
        try {
            int idIdx = cursor.getColumnIndex(CalendarContract.Instances._ID);
            int eventIdIdx = cursor.getColumnIndex(CalendarContract.Instances.EVENT_ID);
            int titleIdx = cursor.getColumnIndex(CalendarContract.Instances.TITLE);
            int beginIdx = cursor.getColumnIndex(CalendarContract.Instances.BEGIN);
            int endIdx = cursor.getColumnIndex(CalendarContract.Instances.END);
            int allDayIdx = cursor.getColumnIndex(CalendarContract.Instances.ALL_DAY);
            int descIdx = cursor.getColumnIndex(CalendarContract.Instances.DESCRIPTION);
            int ownerEmailIdx = cursor.getColumnIndex(CalendarContract.Instances.ORGANIZER);

            List<Event> events = new ArrayList<Event>(cursor.getCount());
            while (cursor.moveToNext()) {
                Event event = new Event();
                event.id = cursor.getLong(idIdx);
                event.eventId = cursor.getLong(eventIdIdx);
                event.title = cursor.getString(titleIdx);
                event.begin = cursor.getLong(beginIdx);
                event.end = cursor.getLong(endIdx);
                event.allDay = cursor.getInt(allDayIdx) != 0;
                event.description = cursor.getString(descIdx);
                String ownerEmail = cursor.getString(ownerEmailIdx);
                Cursor contactCursor = contentResolver.query(Data.CONTENT_URI,
                        CONTACT_PROJECTION, CONTACT_SELECTION, new String[] {ownerEmail}, null);
                int ownerIdIdx = contactCursor.getColumnIndex(Data.CONTACT_ID);
                long ownerId = -1;
                if (contactCursor.moveToFirst()) {
                    ownerId = contactCursor.getLong(ownerIdIdx);
                }
                contactCursor.close();
                // Use event organizer's profile picture as the notification background.
                event.ownerProfilePic = getProfilePicture(contentResolver, context, ownerId);
                events.add(event);
            }
            return events;
        } finally {
            cursor.close();
        }
    }

    @Override
    public void onConnected(Bundle connectionHint) {
    }

    @Override
    public void onConnectionSuspended(int cause) {
    }

    @Override
    public void onConnectionFailed(ConnectionResult result) {
    }

    private static Asset getDefaultProfile(Resources res) {
        Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.nobody);
        return Asset.createFromBytes(toByteArray(bitmap));
    }

    private static Asset getProfilePicture(ContentResolver contentResolver, Context context,
                                           long contactId) {
        if (contactId != -1) {
            // Try to retrieve the profile picture for the given contact.
            Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
            InputStream inputStream = Contacts.openContactPhotoInputStream(contentResolver,
                    contactUri, true /*preferHighres*/);

            if (null != inputStream) {
                try {
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                    if (bitmap != null) {
                        return Asset.createFromBytes(toByteArray(bitmap));
                    } else {
                        Log.e(TAG, "Cannot decode profile picture for contact " + contactId);
                    }
                } finally {
                    closeQuietly(inputStream);
                }
            }
        }
        // Use a default background image if the user has no profile picture or there was an error.
        return getDefaultProfile(context.getResources());
    }

    private static byte[] toByteArray(Bitmap bitmap) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
        byte[] byteArray = stream.toByteArray();
        closeQuietly(stream);
        return byteArray;
    }

    private static void closeQuietly(Closeable closeable) {
        try {
            closeable.close();
        } catch (IOException e) {
            Log.e(TAG, "IOException while closing closeable.", e);
        }
    }

    private static class Event {

        public long id;
        public long eventId;
        public String title;
        public long begin;
        public long end;
        public boolean allDay;
        public String description;
        public Asset ownerProfilePic;

        public PutDataMapRequest toPutDataMapRequest(){
            final PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(
                    makeDataItemPath(eventId, begin));
            DataMap data = putDataMapRequest.getDataMap();
            data.putString(DATA_ITEM_URI, putDataMapRequest.getUri().toString());
            data.putLong(ID, id);
            data.putLong(EVENT_ID, eventId);
            data.putString(TITLE, title);
            data.putLong(BEGIN, begin);
            data.putLong(END, end);
            data.putBoolean(ALL_DAY, allDay);
            data.putString(DESCRIPTION, description);
            data.putAsset(PROFILE_PIC, ownerProfilePic);

            return putDataMapRequest;
        }
    }
}
